import { randomBytes } from '@stablelib/random'
import { generateKeyPairFromSeed, marshalPrivateKey, marshalPublicKey } from '@libp2p/crypto/keys'
import { peerIdFromKeys } from '@libp2p/peer-id'
import { DID } from 'dids'
import { Ed25519Provider } from 'key-did-provider-ed25519'
import { peerIdFromString } from '@libp2p/peer-id'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { unmarshalPublicKey } from '@libp2p/crypto/keys';
import KeyDidResolver from 'key-did-resolver'
import { CID } from 'multiformats/cid'
import * as bip39 from 'bip39'

import {Resolver} from 'did-resolver'

export function generateMnemonic() {
  let mnemonic = bip39.generateMnemonic() 
  return mnemonic
}

export function generateSeed() {
    return randomBytes(32);
}

export function seedToHex(seed) {
    return Buffer.from(seed).toString('hex');
}

export function seedHexToSeed(seedHex) {
    return new Uint8Array(Buffer.from(seedHex, 'hex'));
}

export async function mnemonicToSeed(mnemonic) {
  console.log('mnemonicToSeed', '#' + mnemonic + '#')
  let seed = await bip39.mnemonicToSeed(mnemonic)
  return seed.slice(0, 32)
}

export async function initDid(seed) {
    const provider = new Ed25519Provider(seed)
    const did = new DID({ provider, resolver: KeyDidResolver.getResolver() })
    
    // Authenticate with the provider
    await did.authenticate()
    return did
}

export async function isValidDid(did) {
  const keyDidResolver = KeyDidResolver.getResolver()
  const didResolver = new Resolver(keyDidResolver)
  try {
    const doc = await didResolver.resolve(did)
    if (!doc || !doc.didDocument) return false
  } catch(err) {
    return false
  }
  return true
}

export const addEncryptedData = async (ipfs, did, targetDids, cleartext) => {
  const jwe = await did.createDagJWE(cleartext, [...targetDids, did.id])
  // put the JWE into the ipfs dag
  const jweCid = await ipfs.dag.put(jwe, { storeCodec: 'dag-jose', hashAlg: 'sha2-256' })
  return jweCid
}

export const decryptJWE = async (ipfs, did, jweCid) => {
  // console.log('getting data from ipfs')
  const jweData = await ipfs.dag.get(CID.parse(jweCid), { timeout: 2000 })
  // console.log('jweData', jweData)
  const dagJWE = jweData.value
  // console.log('call decryptDagJWE')
  return await did.decryptDagJWE(dagJWE)
}

export const createEd25519PeerId = async (seed) => {
  const key = await generateKeyPairFromSeed('Ed25519', seed)
  const id = await createFromPrivKey(key)

  if (id.type === 'Ed25519') {
    return id
  }

  throw new Error(`Generated unexpected PeerId type "${id.type}"`)
}

async function createFromPrivKey (privateKey) {
    return await peerIdFromKeys(marshalPublicKey(privateKey.public), marshalPrivateKey(privateKey))
}

export const peerIdToDid = (peerId) => {
    const peer = peerIdFromString(peerId);
    return encodeDID(peer.publicKey)
}

function encodeDID(publicKey) {
    {/* @ts-ignore */}
    const pubKey = unmarshalPublicKey(publicKey)._key;
    const bytes = new Uint8Array(pubKey.length + 2);
    bytes[0] = 0xed; // ed25519 multicodec
    // The multicodec is encoded as a varint so we need to add this.
    // See js-multicodec for a general implementation
    bytes[1] = 0x01;
    bytes.set(pubKey, 2);
    return `did:key:z${uint8ArrayToString(bytes, 'base58btc')}`;
}